/* caStore.c */

/*
 * Licensed to the ConArtist Development Team (CADT).
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 * The CADT licenses this file to you under the Apache License, 
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <string.h>
#include <stdio.h>

#include <apr.h>
#include <apr_dso.h>
#include <apr_file_info.h>
#include <apr_thread_rwlock.h>
#include <apr_strings.h>

#include "conartist.h"
#include "caStore.h"
#include "config.h"
#include "caLog.h"

#include "caPrivate.h"

#ifdef HAVE_REALPATH
#  include <limits.h>
#  include <stdlib.h>
#endif

static apr_hash_t *dsoHash = NULL;
static apr_thread_rwlock_t *modLock = NULL;
static apr_pool_t *dsoPool = NULL;

struct dsoEntry {
    char *filename;
    apr_dso_handle_t *dso;
    struct caModule *ptr;
};

apr_status_t dsoInit(void)
{
    apr_status_t rv = apr_pool_create(&dsoPool, globalPool);
    if (rv != APR_SUCCESS)
        return rv;
    dsoHash = apr_hash_make(dsoPool);
    if (!dsoHash)
        return APR_ENOMEM;
    rv = apr_thread_rwlock_create(&modLock, dsoPool);
    if (rv != APR_SUCCESS) {
        apr_pool_destroy(dsoPool);
        return rv;
    }
    return rv;
}

static struct dsoEntry *checkDSO(const char *path)
{
    struct dsoEntry *de = NULL;
    apr_status_t rv = apr_thread_rwlock_rdlock(modLock);
    if (rv != APR_SUCCESS)
        return NULL;
    de = apr_hash_get(dsoHash, path, APR_HASH_KEY_STRING);
    apr_thread_rwlock_unlock(modLock);
    return de;
}

static apr_status_t insertDSOEntry(struct dsoEntry *de)
{
    apr_status_t rv;

    if (!de)
        return APR_EINVAL;

    rv = apr_thread_rwlock_wrlock(modLock);
    if (rv == APR_SUCCESS) {
        apr_hash_set(dsoHash, de->filename, APR_HASH_KEY_STRING, de);
        return apr_thread_rwlock_unlock(modLock);
    }
    return rv;
}

static apr_status_t loadDSO(const char *path)
{
    apr_dso_handle_t *dso;
    apr_dso_handle_sym_t sym;
    apr_status_t rv;
    struct caModule *cm = NULL;
    struct dsoEntry *de = NULL;

    de = checkDSO(path);
    if (de)
        return APR_SUCCESS;

    rv = apr_dso_load(&dso, path, globalPool);
    if (rv != APR_SUCCESS) {
        char err[256];
        apr_dso_error(dso, err, 256);
        /* TODO - add proper logging */
        caLog(CA_LOG_INFO, "dso_load failed for '%s'\n%s\n", path, err);
        return rv;
    }

    rv = apr_dso_sym(&sym, dso, "ConArtistModule");
    if (rv == APR_SUCCESS) {
        cm = (struct caModule *)sym;
        /* Run the module initialisation function */
        if (cm->modInit)
            rv = cm->modInit(globalPool);

        if (rv == APR_SUCCESS) {
            switch (cm->type) {
                case CAM_STORE:
                    rv = addStoreModule(dso, cm);
                    break;
                case CAM_PIPE:
                    //rv = addPipeModule(dso, cm);
                    break;
                default:
                    caLog(CA_LOG_WARN, "Unknown module type '%d'\n", cm->type);
                    break;
            }

            de = apr_pcalloc(dsoPool, sizeof(*de));
            if (de) {
                de->dso = dso;
                de->ptr = cm;
                de->filename = apr_pstrdup(dsoPool, path);
                rv = insertDSOEntry(de);
            } else
                rv = APR_ENOMEM;
            if (rv == APR_SUCCESS)
                caLog(CA_LOG_INFO, "Loaded DSO '%s'", path);
        }
    }

    if (rv != APR_SUCCESS) {
        if (cm && cm->modFini)
            cm->modFini();
        apr_dso_unload(dso);
        return rv;
    }
   
    return rv;
}

int findDSO(const char *path)
{
    apr_dir_t *dir;
    apr_status_t rv;
    apr_finfo_t fi;
    apr_int32_t wanted = APR_FINFO_DIRENT;

#ifdef HAVE_REALPATH
    char rDir[PATH_MAX];
    path = realpath(path, rDir);
#endif

    /* This catches us being passed path as a NULL pointer or the case
     * where we have realpath support and the directory doesn't exist.
     */
    if (! path)
        return APR_ENOENT;

    rv = apr_dir_open(&dir, path, globalPool);
    if (rv != APR_SUCCESS)
        return rv;

    while ((rv = apr_dir_read(&fi, wanted, dir)) == APR_SUCCESS) {
        char *fullPath;
        /* Skip non-regular files and anything that starts with a '.' */
        if (fi.name[0] == '.' || fi.filetype != APR_REG)
            continue;

        /* Get the full path to the file. Why doesn't APR have realpath? */
        rv = apr_filepath_merge(&fullPath, path, fi.name, 
                                APR_FILEPATH_NATIVE, globalPool);
        if (rv != APR_SUCCESS)
            continue;

        rv = loadDSO(fullPath);
        if (rv != APR_SUCCESS)
            caLog(CA_LOG_WARN, "Unable to load '%s'\n", fullPath);

    }

    apr_dir_close(dir);
    return APR_SUCCESS;
}
